/*
 * File:    WrappedNode.java
 * Created: 06-Feb-2009
 * Version: $Id$
 *
 * COPYRIGHT (C) 2009, Bitgate Mobile, LLC.  All Rights Reserved.
 *
 * software@bitgatemobile.com
 */

package com.webplasm.render.tags;

import static com.bitgate.util.debug.Debug.debug;
import static org.w3c.dom.Node.CDATA_SECTION_NODE;
import static org.w3c.dom.Node.ELEMENT_NODE;
import static org.w3c.dom.Node.TEXT_NODE;

import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.webplasm.context.RenderContext;
import com.webplasm.render.convert.ConvertTagCache;
import com.webplasm.render.engine.RenderException;
import com.webplasm.render.tags.internal.Document;
import com.webplasm.render.tags.internal.Text;
import com.webplasm.store.VariableStoreInterface;

/**
 * This class is used to take the contents of a SAX-parsed XML document <code>Node</code> object, and wrap it so that
 * tag libraries can be called to render its contents.  This is used by the <code>RenderEngine</code> class, which
 * automatically wraps the root node.  Once the root node is wrapped by the <code>RenderEngine</code>, it automatically
 * traverses down the document and wraps each node with the appropriate tag code.  This way, once the
 * <code>render()</code> function is called, the nodes are walked, the data is rendered, and the document is returned
 * after parsing completes.
 * <p/>
 * This code is a rewrite of Webplasm 1.0's rendering engine.
 * 
 * @author kenji
 * @since Webplasm 2.0
 */
public class WrappedNode implements RenderInterface {
	public Vector<WrappedNode> children = new Vector<WrappedNode>();
	public Node thisNode;
	protected Map<String, String> elements = new HashMap<String, String>();
	protected StringBuffer data;
	protected String tagName;
	
	public WrappedNode() {
		// Empty constructor.
	}
	
	/**
	 * Constructor, used to wrap the given <code>Node</code> object.
	 * 
	 * @param node The <code>Node</code> to wrap.
	 */
	public WrappedNode(Node node) {
		thisNode = node;
		prepareTag(node);
	}
	
	/**
	 * This is a static helper class function that is used to wrap a <code>Node</code> object internally such that
	 * it returns a parsable <code>WrappedNode</code> object for rendering.
	 * 
	 * @param tag The <code>Node</code> to wrap.
	 * @returns <code>WrappedNode</code> object that can be rendered.
	 */
	private static WrappedNode getNode(Node tag) {
		if (tag == null) {
			return null;
		}
		
		try {
			switch(tag.getNodeType()) {
				case TEXT_NODE:
				case CDATA_SECTION_NODE:
					return new Text(tag);
					
				case ELEMENT_NODE:
					String tagValue = tag.getNodeName();
					String namespaceValue = null;
					
					if (tagValue.indexOf(":") != -1) {
						namespaceValue = tagValue.substring(0, tagValue.indexOf(":"));
						tagValue = tagValue.substring(tagValue.indexOf(":") + 1);
					}
					
					if (namespaceValue != null) {
						if (tagValue.equalsIgnoreCase("document")) {
							// Return Document object here.
							debug("*** Document tag detected, cannot be used yet.");
							return null;
						} else if (tagValue.equalsIgnoreCase("documentbody")) {
							// Return DocumentBody object here.
							debug("*** DocumentBody tag detected, cannot be used yet.");
							return null;
						} else {
							Class<WrappedNode> docObject = ConvertTagCache.getDefault().getTagClass(tag.getNodeName());
							WrappedNode tagObject = null;
							
							if (docObject == null) {
								debug("Cannot resolve element for namespace '" + namespaceValue + "', element '" +
										tagValue + "', returning null object.");
								return null;
							}
							
							try {
								tagObject = docObject.newInstance();
								tagObject.prepareTag(tag);
								tagObject.thisNode = tag;
							} catch(Exception ex) {
								debug("Unknown namespace '" + namespaceValue + "' or exception occurred: " + ex);
								return new Text(tag);
							}
							
							NamedNodeMap nodeList = tag.getAttributes();
							
							if (nodeList != null) {
								for(int i = 0; i < nodeList.getLength(); i++) {
									Node nodeItem = nodeList.item(i);
									String nodeName = nodeItem.getNodeName();
									String nodeValue = nodeItem.getNodeValue();
									
									tagObject.elements.put(nodeName, nodeValue);
								}
							}
							
							debug("Returning tag class for tag '" + tag.getNodeName() + "' elements=" +
									tagObject.elements + "'");
							
							return tagObject;
						}
					}
					
					return new Text(tag);
					
				default:
					debug("Unknown element type: " + tag.getNodeType());
					return null;
			}
		} catch(RuntimeException ex) {
			debug("Bad element exception: " + ex.getMessage());
		}
		
		return null;
	}
	
	/**
	 * Returns a boolean value indicating whether or not the tag children of the current <code>WrappedNode</code>
	 * can be recursed and rendered.  If this function is overridden by the tag class, it will most likely return
	 * a <code>false</code>.  This is generally done where the tag manages its own children.
	 * 
	 * @return <code>true</code> if recursion is possible, <code>false</code> if recursion is performed manually.
	 */
	public boolean canRecurse() {
		return true;
	}
	
	/**
	 * Renders the document from the current node point in the XML tree.
	 * 
	 * @param rContext The currently active <code>RenderContext</code> object.
	 * @return <code>StringBuffer</code> after rendering.
	 * @throws RenderException on any errors.
	 */
	public StringBuffer render(RenderContext rContext) throws RenderException {
		StringBuffer result = new StringBuffer();
		
		for(WrappedNode wrappedNode : children) {
			StringBuffer res;
			
			if ((res = wrappedNode.render(rContext)) == null) {
				return null;
			}
			
			result.append(res);
		}
		
		debug("Rendering '" + result.toString() + "'");
		return result;
	}
	
	/**
	 * Prepares a <code>Node</code> and its children for rendering.
	 * 
	 * @param node <code>Node</code> to wrap.
	 */
	protected void prepareTag(Node node) {
		String value = node.getNodeValue();
		
		data = new StringBuffer((value == null) ? "" : value);
		tagName = node.getNodeName();
		
		if (node.getNodeType() != ELEMENT_NODE) {
			return;
		}
		
		NodeList list = node.getChildNodes();
		int size = list.getLength();
		
		for(int i = 0; i < size; i++) {
			if (list.item(i) == null) {
				debug("Dropping null child in " + tagName + " = '" + data + "'");
				continue;
			}
			
			WrappedNode t = WrappedNode.getNode(list.item(i));
			
			if (t == null) {
				debug("Couldn't parse child element '" + node.getNodeName() + "'");
				throw new RuntimeException("DocumentTag: Bad template element: '" + node.getNodeName() + "'");
			}

			if (t instanceof Document) {
				for(WrappedNode tag : t.children) {
					children.addElement(tag);
				}
			} else {
				children.addElement(t);
			}
		}
	}

	/**
	 * This walks the current node's available children and prepares the sub-nodes for rendering by
	 * wrapping them inside <code>WrappedNode</code> objects.  This makes it so that the underlying tag class can
	 * pull the sub-nodes by name (from the <code>HashMap</code>) and call the <code>render</code> function on
	 * each contents.
	 * 
	 * @param n The current <code>Node</code> to traverse.
	 * @return A <code>Map</code> of <code>WrappedNode</code>s keyed by <code>String</code> names of the sub-nodes.
	 * Returns a blank list if no sub-nodes were found.
	 */
	protected Map<String, WrappedNode> prepareNodes(Node n) {
		HashMap<String, WrappedNode> wrappedNodes = new HashMap<String, WrappedNode>();
		
		if (n == null) {
			return wrappedNodes;
		}
		
		if (!n.hasChildNodes()) {
			return wrappedNodes;
		}
		
		if (n.getNodeType() != Node.ELEMENT_NODE) {
			return wrappedNodes;
		}
		
		NodeList list = n.getChildNodes();
		int size = list.getLength();
		
		for(int i = 0; i < size; i++) {
			if (list.item(i) == null) {
				continue;
			}
			
			Node child = list.item(i);
			
			if (child.getNodeType() == Node.TEXT_NODE) {
				continue;
			}
			
			if (child.getNodeType() != Node.ELEMENT_NODE) {
				return new HashMap<String, WrappedNode>();
			}
			
			String childname = child.getNodeName().toLowerCase();
			
			wrappedNodes.put(childname, new WrappedNode(child));
		}
		
		return wrappedNodes;
	}
	
	/**
	 * Returns either the value of the attribute that was specified by name, or the value of the variable specified
	 * in the contents of the attribute.
	 * 
	 * @param rContext The currently active <code>RenderContext</code>.
	 * @param name The name of the attribute to retrieve.
	 * @return The value of the attribute.
	 */
	protected String getWrappedAttribute(RenderContext rContext, String name) {
		String element = elements.get(name);
		
		if (element == null) {
			return null;
		}
		
		if (element.startsWith("$(") && element.endsWith(")")) {
			String varName = element.substring(2, element.length() - 1);
			
			if (varName.toLowerCase().startsWith("get:")) {
				String getVar = varName.substring(4);
				
				try {
					return rContext.getClientContext().getVariableStore().getGetVariable(getVar);
				} catch(NullPointerException ex) {
					return "";
				}
			} else if (varName.toLowerCase().startsWith("post:")) {
				String postVar = varName.substring(5);
				
				try {
					return rContext.getClientContext().getVariableStore().getPostVariable(postVar);
				} catch(NullPointerException ex) {
					return "";
				}
			}
			
			VariableStoreInterface vsInterface = rContext.getVariableStore().getVariable(varName);
			
			if (vsInterface != null) {
				debug("Varname: name=" + varName + " value=" + vsInterface.getString());
			
				return vsInterface.getString();
			}
		}
		
		return element;
	}
}